import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class FizzBuzz {
    private int n;

    private Semaphore fizzSem,buzzSem,fizzbuzzSem,numberSem;

    private int fizzCount,buzzCount,fizzbuzzCount;

    int getModCount(int mod){
        int res=0;
        for (int i = mod; i <=n ; i+=mod) {
            res++;
        }
        return res;
    }

    public FizzBuzz(int n) {
        this.n = n;
        fizzSem=new Semaphore(0);
        buzzSem=new Semaphore(0);
        fizzbuzzSem=new Semaphore(0);
        numberSem=new Semaphore(1);
        fizzbuzzCount=getModCount(15);
        fizzCount=getModCount(3)-fizzbuzzCount;
        buzzCount=getModCount(5)-fizzbuzzCount;
    }

    // printFizz.run() outputs "fizz".
    public void fizz(Runnable printFizz) throws InterruptedException {
        for (int i = 0; i < fizzCount; i++) {
            fizzSem.acquire();
            printFizz.run();
            numberSem.release();
        }
    }

    // printBuzz.run() outputs "buzz".
    public void buzz(Runnable printBuzz) throws InterruptedException {
        for (int i = 0; i < buzzCount; i++) {
            buzzSem.acquire();
            printBuzz.run();
            numberSem.release();
        }
    }

    // printFizzBuzz.run() outputs "fizzbuzz".
    public void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
        for (int i = 0; i < fizzbuzzCount; i++) {
            fizzbuzzSem.acquire();
            printFizzBuzz.run();
            numberSem.release();
        }
    }

    // printNumber.accept(x) outputs "x", where x is an integer.
    public void number(IntConsumer printNumber) throws InterruptedException {
        for (int i = 1; i <= n; i++) {
            numberSem.acquire();
            boolean isFizz=(i%3)==0;
            boolean isBuzz=(i%5)==0;
            if(isFizz&&isBuzz){
                fizzbuzzSem.release();
            }
            else if(isFizz){
                fizzSem.release();
            }
            else if(isBuzz){
                buzzSem.release();
            }
            else{
                printNumber.accept(i);
                numberSem.release();
            }
        }
    }

    public static void main(String[] args) {
        FizzBuzz fb=new FizzBuzz(65);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    fb.fizz(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("fizz");
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    fb.buzz(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("buzz");
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    fb.fizzbuzz(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("fizzbuzz");
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    fb.number(new IntConsumer());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}